//
// Created on 2022/8/5.
//

#include <node_api.h>
#include <iostream>
#include <memory>

#include "i_serialport_client.h"
#include "log/serialport_log_wrapper.h"

#define DEFAULT_ARG_COUNT 8
#define CALLBACK_ARGV_CNT 2
#define ARG_BUFFER_SIZE 512

using namespace OHOS::SerialPort;

struct AsyncCallInfo{
    napi_env env = nullptr;
    napi_ref callbackRef = nullptr;
    napi_deferred deferred = nullptr;
    napi_async_work work = nullptr;
    void *data = nullptr;
};

static napi_value get_error_value(napi_env env, const char *errString)
{
    napi_value error = nullptr;
    napi_value errCode = nullptr;
    napi_value errMessage = nullptr;
    //SERIALPORT_LOGE("err: %{public}s", err);
    napi_create_string_utf8(env, "err_x_tool", strlen("err_x_tool"), &errCode);
    napi_create_string_utf8(env, errString, strlen(errString), &errMessage);
    napi_create_error(env, errCode, errMessage, &error);
    return error;
}

static void AsyncCallFinish(AsyncCallInfo* asyncCallInfo, int32_t result, napi_value *asyncResult)
{
    if (asyncCallInfo->deferred) {
        if (result == 0) {
            napi_resolve_deferred(asyncCallInfo->env, asyncCallInfo->deferred,
                    asyncResult[1]==nullptr?asyncResult[0]:asyncResult[1]);
        } else {
            napi_reject_deferred(asyncCallInfo->env, asyncCallInfo->deferred, asyncResult[0]);
        }
    } else {
        napi_value callback = nullptr;
        napi_get_reference_value(asyncCallInfo->env, asyncCallInfo->callbackRef, &callback);
        napi_call_function(asyncCallInfo->env, nullptr, callback, CALLBACK_ARGV_CNT, asyncResult, nullptr);
        napi_delete_reference(asyncCallInfo->env, asyncCallInfo->callbackRef);
    }
}

struct OpenSerialValue {
    std::string dev;
    int32_t out = -1;
};

static napi_value Call_OpenSerial(napi_env env, napi_callback_info info)
{
    size_t argc = DEFAULT_ARG_COUNT;
    napi_value thisVar;
    void *thisData;
    napi_value argv[DEFAULT_ARG_COUNT] = {0};
    napi_get_cb_info(env, info, &argc, argv , &thisVar, &thisData);
    if (argc < 1) {
        return get_error_value(env, "param invalid");
    }

    char buf[ARG_BUFFER_SIZE];
    size_t result;
    napi_get_value_string_utf8(env, argv[0], buf, ARG_BUFFER_SIZE, &result);
    napi_valuetype valueType = napi_undefined;
    napi_value retValue = nullptr;
    AsyncCallInfo *asyncCallInfo = new(std::nothrow) AsyncCallInfo;
    if (asyncCallInfo == nullptr) {
        return get_error_value(env, "new AsyncCallInfo failed.");
    }
    OpenSerialValue *openSerialValue = new(std::nothrow) OpenSerialValue;
    if (asyncCallInfo == nullptr) {
        delete asyncCallInfo;
        return get_error_value(env, "new OpenSerialValue failed.");
    }
    asyncCallInfo->data = openSerialValue;
    openSerialValue->dev = buf;
    if (argc > 1) {
        size_t funcIndex = argc-1;
        napi_typeof(env, argv[funcIndex], &valueType);
        if (valueType == napi_function) {
            napi_create_reference(env, argv[funcIndex], 1, &asyncCallInfo->callbackRef);
            napi_get_undefined(env, &retValue);
        } else {
            napi_create_promise(env, &asyncCallInfo->deferred, &retValue);
        }
    } else {
        napi_create_promise(env, &asyncCallInfo->deferred, &retValue);
    }

    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "x_napi_tool", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName,
            [](napi_env env, void* data) {
                AsyncCallInfo* asyncCallInfo = (AsyncCallInfo*)data;
                OpenSerialValue* openValue = (OpenSerialValue*)asyncCallInfo->data;
                openValue->out = OHOS::SerialPort::get_serial_client()->OpenSerial(openValue->dev);
            },
            [](napi_env env, napi_status status, void* data) {
                AsyncCallInfo* asyncCallInfo = (AsyncCallInfo*)data;
                OpenSerialValue* openValue = (OpenSerialValue*)asyncCallInfo->data;
                napi_value asyncResult[CALLBACK_ARGV_CNT]={nullptr, nullptr};
                napi_create_int32(env, openValue->out, &asyncResult[0]);
                AsyncCallFinish(asyncCallInfo, openValue->out,asyncResult);
                napi_delete_async_work(env, asyncCallInfo->work);
                delete openValue;
                delete asyncCallInfo;
            },
            (void*)asyncCallInfo, &asyncCallInfo->work);
    napi_queue_async_work(env, asyncCallInfo->work);
    return retValue;
}

struct CloseSerialValue{
    std::string dev;
    int32_t out = -1;
};
static napi_value Call_CloseSerial(napi_env env, napi_callback_info info) {
    size_t argc = DEFAULT_ARG_COUNT;
    napi_value argv[DEFAULT_ARG_COUNT] = {0};
    napi_get_cb_info(env, info, &argc, argv , nullptr, nullptr);
    if (argc < 1) {
        return get_error_value(env, "param invalid");
    }

    char buf[ARG_BUFFER_SIZE];
    size_t result = 0;
    napi_get_value_string_utf8(env, argv[0], buf, ARG_BUFFER_SIZE, &result);
    napi_valuetype valueType = napi_undefined;
    napi_value retValue = nullptr;
    AsyncCallInfo *asyncCallInfo = new(std::nothrow) AsyncCallInfo;
    if (asyncCallInfo == nullptr) {
        return get_error_value(env, "new AsyncCallInfo failed.");
    }
    CloseSerialValue *closeSerialValue = new(std::nothrow) CloseSerialValue;
    if (asyncCallInfo == nullptr) {
        delete asyncCallInfo;
        return get_error_value(env, "new OpenSerialValue failed.");
    }
    asyncCallInfo->data = closeSerialValue;
    closeSerialValue->dev = buf;

    if (argc > 1) {
        size_t funcIndex = argc-1;
        napi_typeof(env, argv[funcIndex], &valueType);
        if (valueType == napi_function) {
            napi_create_reference(env, argv[funcIndex], 1, &asyncCallInfo->callbackRef);
            napi_get_undefined(env, &retValue);
        } else {
            napi_create_promise(env, &asyncCallInfo->deferred, &retValue);
        }
    } else {
        napi_create_promise(env, &asyncCallInfo->deferred, &retValue);
    }

    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "x_napi_tool", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName,
            [](napi_env env, void* data) {
                AsyncCallInfo* asyncCallInfo = (AsyncCallInfo*)data;
                CloseSerialValue* closeValue = (CloseSerialValue*)asyncCallInfo->data;
                closeValue->out = OHOS::SerialPort::get_serial_client()->CloseSerial(closeValue->dev);
            },
            [](napi_env env, napi_status status, void* data) {
                AsyncCallInfo* asyncCallInfo = (AsyncCallInfo*)data;
                CloseSerialValue* closeValue = (CloseSerialValue*)asyncCallInfo->data;
                napi_value asyncResult[CALLBACK_ARGV_CNT]={nullptr, nullptr};
                napi_create_int32(env, closeValue->out, &asyncResult[0]);
                AsyncCallFinish(asyncCallInfo, closeValue->out,asyncResult);
                napi_delete_async_work(env, asyncCallInfo->work);
                delete closeValue;
                delete asyncCallInfo;
            },
            (void*)asyncCallInfo, &asyncCallInfo->work);
    napi_queue_async_work(env, asyncCallInfo->work);
    return retValue;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "openSerial", nullptr, Call_OpenSerial, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "closeSerial", nullptr, Call_CloseSerial, nullptr, nullptr, nullptr, napi_default, nullptr }
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module heplerModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "libserialhelper",
.nm_priv = ((void*)0),
.reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterModule(void)
{
napi_module_register(&heplerModule);
}